# -*- coding: utf-8 -*-

"""
Module implementing LayerDataDialog.
"""

import copy
import re
from PyQt4.QtGui import *
from PyQt4.QtCore import *
import geolib.utm as utm
import geolib.geomeasure as geomeasure

from ui_gen.Ui_data_table_dialog import Ui_MainWindow

class DataExpressionError(object):
    pass

class LayerDataExpression(object):
    """
    Defines an expression used in calculation of calculated columns.
    
    Syntax: fields should be referred to in sql format (e.g. `field_name`). Evaluated using
    eval(), with "from math import *" evaluated first. Insert geometry using the following:
    
    geometry:point_lat
            :point_lon
            :point_utmE(zone=auto)
            :point_utmN(zone=auto)
            :point_utmZone
    
    for lines:
    geometry:line_length(unit=m)
    
    for polygon:
    geometry:poly_area(unit=m2)
    geometry:poly_perimeter(unit=m)
    """
    
    REFIELD = re.compile("`(.*)`")
    REGEOM = re.compile("geometry:([^_]*)_(.*)")
    REARGS = re.compile("[^(]*\(([^)]*)\)")
    
    def __init__(self, layer, data, stringexpr):
        self.layer = layer
        self.data = data
        self.stringexpr = stringexpr
        
    def evaluate(self, itemId):
        expression = self.stringexpr
        for ref in self.REFIELD.finditer(self.stringexpr):
            try:
                field = ref.strip("`")
                value = self.data[itemId][field]
                expression = expression.replace(ref, value)
            except KeyError:
                return None
    
    def __parsegeometry(self, expression, itemId):
        for ref in self.GEOMFIELD.find(expression):
            geomtype, command = ref.group(1, 2)
            argsmatch = self.REARGS.match()
            if argsmatch:
                args = argsmatch.group(1).split(",")
            else:
                args = []
            value = self.__parsegeocommand(geomtype, command, args, itemId)
            if value:
                expression = expression.replace(ref, value)
            
    def __parsegeocommand(self, geomtype, command, args, itemId):
        if geomtype == "point":
            if self.layer.isSubTypeOf("MapFrameLayer|ItemizedPointLayer"):
                pt = self.layer.points[itemId]
            elif self.layer.isSubTypeOf("MapFrameLayer|LineStringLayer"):
                pt = self.layer.pointSeries[itemId].centre()
            elif self.layer.isSubTypeOf("MapFrameLayer|PolygonLayer"):
                pt = self.layer.polygon[itemId].centre()
            else:
                return None
            
            if command == "lat":
                return pt[1]
            elif command == "lon":
                return pt[0]
            elif command == "utmE":
                if args:
                    return utm.toUtm(pt, utm.parseZone(args[0]))[0][0]
                else:
                    return utm.toUtm(pt)[0][0]
            elif command == "utmN":
                if args:
                    return utm.toUtm(pt, utm.parseZone(args[0]))[0][0]
                else:
                    return utm.toUtm(pt)[0][0]
            elif command == "utmZone":
                utm.formatZone(utm.toUtm(pt, None)[1])
            else:
                return None
        elif geomtype == "line":
            if self.layer.isSubTypeOf("MapFrameLayer|LineStringLayer"):
                lineString = self.layer.pointSeries[itemId]
            else:
                return None
            if command == "length":
                if args:
                    return geomeasure.distance(lineString, args[0])
                else:
                    return geomeasure.distance(lineString)
            else:
                return None
        elif geomtype == "poly":
            if self.layer.isSubTypeOf("MapFrameLayer|PolygonLayer"):
                poly = self.layer.polygon[itemId]
            else:
                return None
            
            if command == "area":
                if args:
                    return geomeasure.area(poly, args[0])
                else:
                    return geomeasure.area(poly)
            elif command == "perimeter":
                if args:
                    return geomeasure.distance(poly.points() + poly.point(0), args[0])
                else:
                    return geomeasure.distance(poly.points() + poly.point(0))
            else:
                return None
        else:
            return None
        
        

class LayerDataDialog(QMainWindow, Ui_MainWindow):
    """
    Class documentation goes here.
    """
    def __init__(self, parent = None):
        """
        Constructor
        """
        QMainWindow.__init__(self, parent)
        self.setupUi(self)
        self.layer = None
        self.model = LayerDataModel(self)
        self.setLayer(None)
        self.tableView.setModel(self.model)
        hohead = self.tableView.horizontalHeader()
        verthead = self.tableView.verticalHeader()
        hohead.show()
        verthead.show()
        
    
    def setLayer(self, layer):
        self.layer = layer
        self.model.setLayer(layer)
    
    @pyqtSignature("")
    def on_buttonAddColumn_clicked(self):
        """
        Slot documentation goes here.
        """
        # TODO: not implemented yet
        raise NotImplementedError
    
    @pyqtSignature("")
    def on_buttonClear_clicked(self):
        """
        Slot documentation goes here.
        """
        # TODO: not implemented yet
        raise NotImplementedError
    
    @pyqtSignature("")
    def on_buttonSave_clicked(self):
        """
        Slot documentation goes here.
        """
        if not self.model.dirty:
            return
        if self.question("Save changes to data in this layer?"):
            self.model.submitLayer()
    
    def question(self, question):
        #TODO make question dialog
        return QMessageBox.question(self, QString("Confirm"), QString(question), 
                                    buttons=QMessageBox.Yes|QMessageBox.No, 
                                    defaultButton=QMessageBox.NoButton) == QMessageBox.Yes

class LayerDataModel(QAbstractTableModel):
    
    def __init__(self, parent=None):
        super(LayerDataModel, self).__init__(parent)
        self.dirty = False
        self.layer = None
        self.ids = []
        self.columns = []
        
    
    def setLayer(self, layer):
        self.beginResetModel()
        self.layer = layer
        if layer is not None:
            self.datadict = copy.deepcopy(layer.data)
            self.columns = list(layer.fields)
            self.ids = list(layer.idsList)
        else:
            self.datadict = {}
        self.dirty = False
        self.endResetModel()
        
    def revertLayer(self):
        self.setLayer(self.layer)
        return True
    
    def submitLayer(self):
        self.layer.data = self.datadict.copy()
        self.layer.fields = list(self.columns)
        self.dirty = False
        return True
    
    def addColumn(self, name):
        if not name in self.columns:
            self.beginInsertColumns()
            self.columns.append(name)
            self.endInsertColumns()
            self.dirty = True
    
    def delColumn(self, name):
        if name in self.columns:
            self.beginRemoveColumns()
            self.columns.remove(name)
            self.endRemoveColumns()
    
    def notifyItemAdded(self, itemId):
        self.beginInsertRows()
        self.ids.append(itemId)
        self.datadict[itemId] = self.layer.data[itemId].copy()
        self.endInsertRows()
        
    def notifyItemRemoved(self, itemId):
        self.beginRemoveRows()
        try:
            self.ids.remove(itemId)
            del self.datadict[itemId]
        except ValueError:
            pass
    
    def rowCount(self, index=QModelIndex()):
        return len(self.ids)
    
    def columnCount(self, index=QModelIndex()):
        return len(self.columns)
    
    def headerData(self, section, orientation, role):
        if orientation == Qt.Horizontal:
            header = unicode(self.columns[section])
            return header
        elif orientation == Qt.Vertical:
            header = unicode(self.ids[section])
            return header
        else:
            return QVariant()
    
    def data(self, index, role):
        if role==Qt.DisplayRole:
            itemId = self.ids[index.row()]
            column = self.columns[index.column()]
            return QVariant(self.datadict[itemId][column])
        else:
            return QVariant()

        